//-----------------------------------------------------------------------------
// Filename: UASInviteTransaction.cs
//
// Description: SIP Transaction that implements UAS (Sser Agent Server) functionality for
// an INVITE transaction.
// 
// History:
// 21 Nov 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using mimosa.SIP;
using mimosa.Utilities;
using mimosa.Crypto;
using mimosa.Net;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace mimosa.SIP.Transactions
{
	/// <summary>
	/// A response matches a client transaction under two conditions:
	///
	/// 1.  If the response has the same value of the branch parameter in
	/// the top Via header field as the branch parameter in the top
	/// Via header field of the request that created the transaction.
    ///
	/// 2.  If the method parameter in the CSeq header field matches the
	/// method of the request that created the transaction.  The
	/// method is needed since a CANCEL request constitutes a
	/// different transaction, but shares the same value of the branch
	/// parameter.
    ///
	/// </summary>
	public class UASInviteTransaction : SIPTransaction
    {
        private static string m_sipProxyUserAgent = SIPConstants.SIP_PROXYUSERAGENT_STRING;

        private bool m_authenticationRequired = false;
        private string m_realm;

        public event SIPTransactionInviteRequestReceived InviteRequestReceived;

        public UASInviteTransaction(
            SIPRequest sipRequest,
            bool authenticationRequired,
            string realm)
            : base(sipRequest)
		{
            m_authenticationRequired = authenticationRequired;
            m_realm = realm;
            TransactionType = SIPTransactionTypesEnum.Invite;
            sipRequest.Header.To.ToTag = CallProperties.CreateNewTag();
        }

        public override void GotRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPRequest sipRequest)
        {
            try
            {
                if (TransactionState == SIPTransactionStatesEnum.Terminated)
                {
                    logger.Debug("Request received by UASInviteTransaction for a terminated transaction, ignoring.");
                }
                else if (sipRequest.Method == SIPMethodsEnum.ACK)
                {
                    if (TransactionState == SIPTransactionStatesEnum.Completed)
                    {
                        logger.Debug("ACK received transitioning to Completed.");
                        TransactionState = SIPTransactionStatesEnum.Completed;
                    }
                    else if (TransactionState == SIPTransactionStatesEnum.Confirmed)
                    {
                        logger.Debug("Duplicate ACK request, ignoring.");
                    }
                }
                else if (sipRequest.Method == SIPMethodsEnum.INVITE)
                {
                    if (TransactionState != SIPTransactionStatesEnum.Unknown)
                    {
                        logger.Debug("Duplicate INVITE request received by UASInviteTransaction, ignoring.");
                    }
                    else if (TransactionState == SIPTransactionStatesEnum.Unknown && m_authenticationRequired && m_initialRequest.Header.AuthenticationHeader == null)
                    {
                        // 407 Response with a fresh nonce needs to be sent.
                        SIPResponse authReqdResponse = GetAuthReqdResponse(sipRequest.Header, m_realm, Crypto.Crypto.GetRandomInt().ToString());

                        logger.Debug("Sending ProxyAuthenticationRequired response for " + sipRequest.Method + " to " + IPSocket.GetSocketString(remoteEndPoint) + ".");

                        TransactionState = SIPTransactionStatesEnum.Completed;
                        SendFinalResponse(authReqdResponse);
                        base.FireTransactionStateChangedEvent();
                    }
                    else
                    {
                        logger.Debug("Sending Trying INVITE.");
                        TransactionState = SIPTransactionStatesEnum.Trying;
                        SIPResponse tryingResponse = GetInformationalResponse(m_initialRequest.Header, SIPResponseStatusCodesEnum.Trying);
                        SendInformationalResponse(tryingResponse);
                        base.FireTransactionStateChangedEvent();

                        // Process INVITE.
                        if (m_authenticationRequired)
                        {
                            FireTransactionAuthenticationRequiredEvent(localEndPoint, remoteEndPoint, sipRequest.Header.AuthenticationHeader);
                        }
                        else
                        {
                            logger.Debug("Sending Proceeding for INVITE.");
                            TransactionState = SIPTransactionStatesEnum.Proceeding;
                            SIPResponse proceedingResponse = GetInformationalResponse(m_initialRequest.Header, SIPResponseStatusCodesEnum.SessionProgress);
                            SendInformationalResponse(proceedingResponse);
                            base.FireTransactionStateChangedEvent();

                            if (InviteRequestReceived != null)
                            {
                                InviteRequestReceived(this);
                            }
                        }
                    }
                }
                else
                {
                    logger.Error("Unexpected " + sipRequest.Method + " passed to UASInviteTransaction.");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception UASInviteTransaction GotRequest. " + excp.Message);
            }
        }

        public override void GotResponse(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPResponse sipResponse)
        {
            logger.Debug("UASInviteTransaction received unexpected response, " + sipResponse.ReasonPhrase + " from " + IPSocket.GetSocketString(remoteEndPoint) + ", ignoring.");
        }

        private SIPResponse GetAuthReqdResponse(SIPHeader requestHeader, string realm, string nonce)
        {
            try
            {
                if (requestHeader == null)
                {
                    throw new ApplicationException("Cannot build Authorisation Required response from an empty request header.");
                }

                SIPResponse authReqdResponse = new SIPResponse(SIPResponseStatusCodesEnum.ProxyAuthenticationRequired);
                SIPAuthenticationHeader authHeader = new SIPAuthenticationHeader(realm, nonce);

                SIPHeader unauthHeader = new SIPHeader(requestHeader.Contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
                unauthHeader.CSeqMethod = requestHeader.CSeqMethod;
                unauthHeader.Via = requestHeader.Via;
                unauthHeader.ProxyAuthenticate = true;
                unauthHeader.AuthenticationHeader = authHeader;
                unauthHeader.UserAgent = m_sipProxyUserAgent;
                unauthHeader.MaxForwards = Int32.MinValue;

                authReqdResponse.Header = unauthHeader;

                return authReqdResponse;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetAuthReqdResponse. " + excp.Message);
                throw excp;
            }
        }

        public SIPResponse GetOkResponse(SIPHeader requestHeader)
        {
            try
            {
                SIPResponse okResponse = new SIPResponse(SIPResponseStatusCodesEnum.Ok);

                SIPContactHeader contact = new SIPContactHeader(null, SIPURI.ParseSIPURI("sip:" + SIPTransport.GetTransportContact(null).ToString()));

                okResponse.Header = new SIPHeader(contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
                okResponse.Header.CSeqMethod = requestHeader.CSeqMethod;
                okResponse.Header.Via = requestHeader.Via;
                okResponse.Header.Server = m_sipProxyUserAgent;
                okResponse.Header.MaxForwards = Int32.MinValue;

                return okResponse;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetOkResponse. " + excp.Message);
                throw excp;
            }
        }

        

        public SIPResponse GetErrorResponse(SIPHeader requestHeader, SIPResponseStatusCodesEnum responseCode, string reasonPhrase)
        {
            try
            {
                SIPResponse errorResponse = SIPTransport.GetResponse(requestHeader, responseCode, reasonPhrase);
                errorResponse.Header.Server = m_sipProxyUserAgent;

                return errorResponse;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetErrorResponse. " + excp.Message);
                throw excp;
            }
        }
	}
}
